﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Threading;
using System.Windows;
using System.Windows.Threading;

namespace Nova.Utilities
{
    /// <summary>
    /// This is a version of the <see cref="ObservableCollection{T}"/> class that does invokes if necessary 
    /// so that any changes by a background thread are synchronously marshalled to the main UI thread in order
    /// to avoid various threading issues.
    /// </summary>
    public class ObservableCollectionMT<T> : IList<T>, ICollection, INotifyCollectionChanged
    {
        protected IList<T> _collection = new List<T>();
        protected readonly Dispatcher _dispatcher;
        protected ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        /// <summary>
        /// Create a new instance, making certain that it's created on the UI thread.
        /// </summary>
        /// <param name="_collection">An existing _collection to be copied into the new one.</param>
        /// <returns>The new <see cref="ObservableCollectionMT{T}"/>.</returns>
        public static ObservableCollectionMT<T> Create(IEnumerable _collection)
        {
            Dispatcher _dispatcher = (Application.Current != null ? Application.Current.Dispatcher : Dispatcher.CurrentDispatcher);
            if (_dispatcher == null || _dispatcher.CheckAccess())
                return new ObservableCollectionMT<T>(_collection);
            return (ObservableCollectionMT<T>)_dispatcher.Invoke(DispatcherPriority.Send, new Func<ObservableCollectionMT<T>>(delegate { return new ObservableCollectionMT<T>(_collection); }));
        }

        /// <summary>
        /// Create a new instance, making certain that it's created on the UI thread.
        /// </summary>
        /// <returns>The new <see cref="ObservableCollectionMT{T}"/>.</returns>
        public static ObservableCollectionMT<T> Create()
        {
            return Create((IEnumerable)null);
        }

        /// <summary>
        /// Create a new instance, as a thread-safe UI-bindable wrapper around the specified <see cref="ObservableCollection{T}"/>.
        /// </summary>
        /// <param name="_collection">The existing <see cref="ObservableCollection{T}"/> to be wrapped.</param>
        /// <returns>The new <see cref="ObservableCollectionMT{T}"/>.</returns>
        public static ObservableCollectionMT<T> Create(ObservableCollection<T> _collection)
        {
            Dispatcher _dispatcher = (Application.Current != null ? Application.Current.Dispatcher : Dispatcher.CurrentDispatcher);
            if (_dispatcher == null || _dispatcher.CheckAccess())
                return new ObservableCollectionMT<T>(_collection);
            return (ObservableCollectionMT<T>)_dispatcher.Invoke(DispatcherPriority.Send, new Func<ObservableCollectionMT<T>>(delegate { return new ObservableCollectionMT<T>(_collection); }));
        }

        /// <summary>
        /// Create a new instance - this method is protected for internal use, to force the use of <see cref="Create(IEnumerable)"/> instead.
        /// </summary>
        protected ObservableCollectionMT(IEnumerable _collection)
        {
            _dispatcher = (Application.Current != null ? Application.Current.Dispatcher : null);
            if (_collection != null)
            {
                foreach (T item in _collection)
                    Add(item);
            }
        }

        /// <summary>
        /// Create a new instance - this method is protected for internal use, to force the use of <see cref="Create(IEnumerable)"/> instead.
        /// </summary>
        protected ObservableCollectionMT()
            : this(null)
        { }

        /// <summary>
        /// Create a new wrapper instance - this method is protected for internal use, to force the use of <see cref="Create(ObservableCollection{T})"/> instead.
        /// </summary>
        protected ObservableCollectionMT(ObservableCollection<T> _collection)
        {
            _dispatcher = (Application.Current != null ? Application.Current.Dispatcher : null);
            _collection.CollectionChanged += WrappedCollection_Changed;
        }

        /// <summary>
        /// Handle any items added or removed from the wrapped <see cref="ObservableCollection{T}"/> _collection.
        /// </summary>
        protected void WrappedCollection_Changed(object obj, NotifyCollectionChangedEventArgs args)
        {
            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (object item in args.NewItems)
                        Add((T)item);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (object item in args.OldItems)
                        Remove((T)item);
                    break;
            }
        }

        public void Add(T item)
        {
            if (_dispatcher.CheckAccess())
                AddInternal(item);
            else
                _dispatcher.BeginInvoke((Action)(delegate { AddInternal(item); }));
        }

        private void AddInternal(T item)
        {
            _readerWriterLock.EnterWriteLock();
            _collection.Add(item);
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
            _readerWriterLock.ExitWriteLock();
        }

        public void Clear()
        {
            if (_dispatcher.CheckAccess())
                ClearInternal();
            else
                _dispatcher.BeginInvoke((Action)(ClearInternal));
        }

        private void ClearInternal()
        {
            _readerWriterLock.EnterWriteLock();
            _collection.Clear();
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            _readerWriterLock.ExitWriteLock();
        }

        public bool Contains(T item)
        {
            _readerWriterLock.EnterReadLock();
            bool result = _collection.Contains(item);
            _readerWriterLock.ExitReadLock();
            return result;
        }

        public void CopyTo(T[] array, int index)
        {
            _readerWriterLock.EnterReadLock();
            _collection.CopyTo(array, index);
            _readerWriterLock.ExitReadLock();
        }

        public void CopyTo(Array array, int index)
        {
            _readerWriterLock.EnterReadLock();
            ((ICollection)_collection).CopyTo(array, index);
            _readerWriterLock.ExitReadLock();
        }

        public int Count
        {
            get
            {
                _readerWriterLock.EnterReadLock();
                int count = _collection.Count;
                _readerWriterLock.ExitReadLock();
                return count;
            }
        }

        public bool IsSynchronized
        {
            get { return true; }
        }

        public object SyncRoot
        {
            get { return _readerWriterLock; }
        }

        public bool IsReadOnly
        {
            get { return _collection.IsReadOnly; }
        }

        public bool Remove(T item)
        {
            if (_dispatcher.CheckAccess())
                return RemoveInternal(item);
            DispatcherOperation operation = _dispatcher.BeginInvoke(new Func<T, bool>(RemoveInternal), item);
            return (operation.Result != null && (bool)operation.Result);
        }

        private bool RemoveInternal(T item)
        {
            bool successful = false;
            _readerWriterLock.EnterWriteLock();
            int index = _collection.IndexOf(item);
            if (index >= 0)
            {
                successful = _collection.Remove(item);
                if (successful && CollectionChanged != null)
                    CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
            }
            _readerWriterLock.ExitWriteLock();
            return successful;
        }

        public IEnumerator<T> GetEnumerator()
        {
            return _collection.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _collection.GetEnumerator();
        }

        public int IndexOf(T item)
        {
            _readerWriterLock.EnterReadLock();
            int index = _collection.IndexOf(item);
            _readerWriterLock.ExitReadLock();
            return index;
        }

        public void Insert(int index, T item)
        {
            if (_dispatcher.CheckAccess())
                InsertInternal(index, item);
            else
                _dispatcher.BeginInvoke((Action)(delegate { InsertInternal(index, item); }));
        }

        private void InsertInternal(int index, T item)
        {
            _readerWriterLock.EnterWriteLock();
            _collection.Insert(index, item);
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
            _readerWriterLock.ExitWriteLock();
        }

        public void RemoveAt(int index)
        {
            if (_dispatcher.CheckAccess())
                RemoveAtInternal(index);
            else
                _dispatcher.BeginInvoke((Action)(delegate { RemoveAtInternal(index); }));
        }

        private void RemoveAtInternal(int index)
        {
            _readerWriterLock.EnterWriteLock();
            if (index < _collection.Count)
            {
                _collection.RemoveAt(index);
                if (CollectionChanged != null)
                    CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, index));
            }
            _readerWriterLock.ExitWriteLock();
        }

        public T this[int index]
        {
            get
            {
                _readerWriterLock.EnterReadLock();
                T result = _collection[index];
                _readerWriterLock.ExitReadLock();
                return result;
            }
            set
            {
                _readerWriterLock.EnterWriteLock();
                if (index < _collection.Count)
                    _collection[index] = value;
                _readerWriterLock.ExitWriteLock();
            }
        }

        protected void ExecuteOrBeginInvoke(Action action)
        {
            if (_dispatcher == null || _dispatcher.CheckAccess())
                action();
            else
                _dispatcher.Invoke(action);
        }
    }
}
